package com.ziiwee.reduce.rest.utils;

import com.ziiwee.reduce.commons.utils.StringUtil;
import com.ziiwee.reduce.rest.Resource;
import com.ziiwee.reduce.rest.ResourceService;
import com.ziiwee.reduce.rest.condition.Condition;
import com.ziiwee.reduce.rest.condition.parser.DefaultQLConditionParser;
import com.ziiwee.reduce.rest.condition.parser.JsonConditionParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class RestUtil {

    private static final Log log = LogFactory.getLog(RestUtil.class);

    public static Condition parseCondition(Class<?> type, HttpServletRequest request) {
        String conditions = request.getHeader("conditions");
        conditions = StringUtil.hasLength(conditions) ? conditions : request.getHeader("Condition");
        conditions = StringUtil.hasLength(conditions) ? conditions : request.getQueryString();
        Condition condition = parseJsonCondition(type, conditions);
        if (condition != null) {
            return condition;
        }
        return parseCondition(type, conditions);
    }

    public static Condition parseJsonCondition(Class<?> type, String conditions) {
        try {
            if (StringUtil.hasLength(conditions)) {
                conditions = new String(Base64Utils.decodeFromString(conditions));
                return new JsonConditionParser(type).parse(conditions);
            }
        } catch (Exception e) {
            log.debug(e.getMessage(), e);
        }
        return null;
    }

    public static Condition parseCondition(Class<?> type, String queryString) {
        try {
            if (StringUtils.hasLength(queryString)) {
                queryString = queryString.replaceAll("page[=]*[0-9]*", "")
                        .replaceAll("size[=]*[0-9]*", "")
                        .replaceAll("sort[=]*[a-zA-Z_]*[a-zA-Z0-9_]*[,]*[a-zA-Z]*", "");
                String decode = URLDecoder.decode(queryString, "utf-8");
                return new DefaultQLConditionParser(type).parse(decode);
            }
        } catch (Exception e) {
            log.warn(e.getMessage());
        }
        return null;
    }

    public static List<ResolvableType> getTypeArgsByInstance(Object object, Class<?> targetInterface) {
        return getTypeArgsByResolvableType(ResolvableType.forInstance(object), targetInterface);
    }

    public static List<ResolvableType> getTypeArgsByClass(Class<?> cls, Class<?> targetInterface) {
        return getTypeArgsByResolvableType(ResolvableType.forClass(cls), targetInterface);
    }

    public static List<ResolvableType> getTypeArgsByResolvableType(ResolvableType type, Class<?> targetInterface) {
        List<ResolvableType> classes = new ArrayList<>();
        if (Objects.equals(type.resolve(), targetInterface)) {
            classes.addAll(Arrays.asList(type.getGenerics()));
        }
        Class<?> superType = type.getSuperType().resolve();
        if (superType != null && superType != Object.class) {
            classes.addAll(getTypeArgsByResolvableType(type.getSuperType(), targetInterface));
        }
        for (ResolvableType anInterface : type.getInterfaces()) {
            classes.addAll(getTypeArgsByResolvableType(anInterface, targetInterface));
        }
        return classes;
    }

    @SuppressWarnings("unchecked")
    public static Resource<? super Object> asResource(Object object) {
        return (Resource<? super Object>) object;
    }

    @SuppressWarnings("unchecked")
    public static ResourceService<? super Resource<? super Object>, ? super Object> asService(ResourceService<?, ?> service) {
        return (ResourceService<? super Resource<? super Object>, ? super Object>) service;
    }

    @SuppressWarnings("unchecked")
    public static Class<? extends Resource<Object>> asResourceType(Class<?> type) {
        return (Class<? extends Resource<Object>>) type;
    }

    @SuppressWarnings("unchecked")
    public static Class<? extends Resource<Object>> asPersistentResourceType(Class<?> type) {
        return (Class<? extends Resource<Object>>) type;
    }

}
